package com.ease.util {

   import flash.utils.Dictionary;
   import mx.core.IUID;

   /**
    * A specialized set which maintains integrity using the given function
    * provided key.  If the key function is not provided it will have default
    * support for IUID or toString on the object.
    */
   public class KeyedSet extends Set {
      private var _keyFunc:Function;

      public function KeyedSet(keyFunc:Function = null ) {
         super();
         _keyFunc = keyFunc;
      }

      override public function add( item:* ):void {
         if (item == null) {
            return;
         }

         var key:String = getKey(item);
         if (set[key] == null) {
            setLength(length + 1);
         }
         set[key] = item;
      }

      override public function remove( item:* ):void {
         if (item == null) {
            return;
         }

         var key:String = getKey(item);
         if (set[key] != null) {
            setLength(length - 1);
         } else {
            return;
         }
         delete set[key];
      }

      override public function contains( item:* ):Boolean {
         if (item == null) {
            return false;
         }

         var key:String = getKey(item);
         return (set[key] != null);
      }

      /**
       * Executes a test function on each item in the set and removes items that
       * return <code>false</code> for the test function.
       *
       * @param job
       *    The function to execute, in form of:
       *    <code>function job(item:*):Boolean;</code>
       */
      override public function filter( job:Function ):void {
         var items:Array = toArray();
         for each (var item:* in items) {
            if (!job( item )) {
               remove( item );
            }
         }
      }

      /**
       * Invokes the given 'job' function for each item in this set one by one, treating the
       * item as the argument to the method. If the optional arg is provided, it serves as
       * the second argument to the invoked method. The return value contains the values
       * returned by the job, keyed on the items in the set.
       */
      override public function forEach( job:Function, arg:* = null ):Dictionary {
         var returnValueByItem:Dictionary = new Dictionary();
         var returnVal:Object;
         var items:Array = toArray();
         if (arg) {
            for each (var item:* in items) {
               returnVal = job( item, arg );
               returnValueByItem[item] = returnVal;
            }
         } else {
            for each (item in items) {
               returnVal = job( item );
               returnValueByItem[item] = returnVal;
            }
         }
         return returnValueByItem;
      }

      /**
       * Returns an item that does not satisfy the given test function if any such item is
       * found in the set. Otherwise returns null.
       */
      override public function every( test:Function, arg:* = null, breakOn:Boolean = false ):* {
         var items:Array = toArray();
         var item:*;
         if (arg) {
            for each (item in items) {
               if (test( item, arg ) == breakOn) {
                  return item;
               }
            }
         } else {
            for each (item in items) {
               if (test( item ) == breakOn) {
                  return item;
               }
            }
         }
      }

      /**
       * Returns an array containing all of the elements in this set
       */
      override public function toArray():Array {
         return MixedUtil.getValues(set);
      }

      public static function fromArray( array:Array ):Set {
         var instance:KeyedSet = new KeyedSet();
         for each (var item:* in array) {
            instance.add( item );
         }
         return instance;
      }

      private function getKey(item:*):String {
         if (_keyFunc != null) {
            return _keyFunc(item);
         }

         if (item is IUID) {
            return (item as IUID).uid;
         }

         return item.toString();
      }
   } // class

}